package com.ojoin.trade.common.web.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import com.google.common.collect.Lists;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import com.github.miemiedev.mybatis.paginator.domain.PageBounds;
import com.ojoin.trade.common.SqlId;
import com.ojoin.trade.common.exception.DaoException;
import com.ojoin.trade.common.utils.BeanMapUtils;
import com.ojoin.trade.common.utils.UUIDUtils;
import com.ojoin.trade.common.web.dao.BaseDao;
import com.ojoin.trade.common.web.domain.IDentifiable;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

/**
 * 基础Dao接口实现类，实现该类的子类必须设置泛型类型
 */

@Repository("BaseDaoImpl")
public   class BaseDaoImpl<T extends IDentifiable> implements BaseDao<T> {
	@Autowired(required = true )
	@Qualifier("sqlSession")
	protected SqlSession sqlSessionTemplate;
	
	
	@Autowired(required = true )
	@Qualifier("sqlSessionBatch")
	protected SqlSession sqlSessionTemplateBatch;


    @Autowired(required = true )
    @Qualifier("dataSource")
    private DataSource dataSource;

    private JdbcTemplate jdbcTemplate;

	
	public static final String SQLNAME_SEPARATOR = ".";

	/**
	 * @fields sqlNamespace SqlMapping命名空间 
	 */
	private String sqlNamespace = getDefaultSqlNamespace();

	/**
	 * 获取泛型类型的实体对象类全名
	 * @return
	 */
	protected String getDefaultSqlNamespace() {
		Class<?> genericClass = BeanMapUtils.getGenericClass(this.getClass());
		return genericClass == null ? null : genericClass.getName();
	}

	
	/**
	 * 获取泛型类型的实体对象类全名
	 * @return
	 */
	private String getDefaultSqlNamespace(Object entity) {
		Class<?> genericClass = entity.getClass();
		return genericClass == null ? null : genericClass.getName();
	}
	
	public void setSqlNamespaceByEntiy(Object entity){
		this.sqlNamespace=this.getDefaultSqlNamespace(entity);
	}
	
	
	/**
	 * 获取SqlMapping命名空间 
	 * @return SqlMapping命名空间 
	 */
	public String getSqlNamespace() {
		return sqlNamespace;
	}

	/**
	 * 设置SqlMapping命名空间。 以改变默认的SqlMapping命名空间，
	 * 不能滥用此方法随意改变SqlMapping命名空间。 
	 * @param sqlNamespace SqlMapping命名空间 
	 */
	public void setSqlNamespace(String sqlNamespace) {
		this.sqlNamespace = sqlNamespace;
	}

	/**
	 * 将SqlMapping命名空间与给定的SqlMapping名组合在一起。
	 * @param sqlName SqlMapping名 
	 * @return 组合了SqlMapping命名空间后的完整SqlMapping名 
	 */
	public String getSqlName(String sqlName) {
		return sqlNamespace + SQLNAME_SEPARATOR + sqlName;
	}

	/**
	 * 生成主键值。 默认使用{@link UUIDUtils#create()}方法
	 * 如果需要生成主键，需要由子类重写此方法根据需要的方式生成主键值。
	 */
	protected String generateId() {
		return UUIDUtils.create();
	}

	@Override
	public <V extends T> V selectOne(T query) {
		Assert.notNull(query);
		try {
			Map<String, Object> params = BeanMapUtils.toMap(query);
			return sqlSessionTemplate.selectOne(getSqlName(SqlId.SQL_SELECT), params);
		} catch (Exception e) {
			throw new DaoException(String.format("查询一条记录出错！语句：%s", getSqlName(SqlId.SQL_SELECT)), e);
		}
	}

	@Override
	public <V extends T> V selectById(int id) {
		Assert.notNull(id);
		try {
			return sqlSessionTemplate.selectOne(getSqlName(SqlId.SQL_SELECT_BY_ID), id);
		} catch (Exception e) {
			throw new DaoException(String.format("根据ID查询对象出错！语句：%s", getSqlName(SqlId.SQL_SELECT_BY_ID)), e);
		}
	}

	@Override
	public <V extends T> List<V> selectList(T query) {
		try {
			Map<String, Object> params = BeanMapUtils.toMap(query);
			return sqlSessionTemplate.selectList(getSqlName(SqlId.SQL_SELECT), params);
		} catch (Exception e) {
			throw new DaoException(String.format("查询对象列表出错！语句：%s", getSqlName(SqlId.SQL_SELECT)), e);
		}
	}

	@Override
	public <V extends T> List<V> selectAll() {
		try {
			return sqlSessionTemplate.selectList(getSqlName(SqlId.SQL_SELECT_ALL));
		} catch (Exception e) {
			throw new DaoException(String.format("查询所有对象列表出错！语句：%s", getSqlName(SqlId.SQL_SELECT_ALL)), e);
		}
	}

	@Override
	public <K, V extends T> Map<K, V> selectMap(T query, String mapKey) {
		Assert.notNull(mapKey, "[mapKey] - must not be null!");
		try {
			Map<String, Object> params = BeanMapUtils.toMap(query);
			return sqlSessionTemplate.selectMap(getSqlName(SqlId.SQL_SELECT), params, mapKey);
		} catch (Exception e) {
			throw new DaoException(String.format("查询对象Map时出错！语句：%s", getSqlName(SqlId.SQL_SELECT)), e);
		}
	}

	/**
	 * 设置分页
	 * @param pageable 分页信息
	 * @return SQL分页参数对象
	 */
	protected RowBounds getRowBounds(Pageable pageable) {
		RowBounds bounds = RowBounds.DEFAULT;
		if (null != pageable) {
			bounds = new RowBounds(pageable.getOffset(), pageable.getPageSize());
		}
		return bounds;
	}
	
	/**
	 * 设置分页
	 * @param pageable 分页信息
	 * @return SQL分页参数对象
	 */
	protected PageBounds getPageBounds(Pageable pageable) {
		PageBounds  bounds;
		if(null==pageable){
			bounds = new PageBounds();
		}else{
			bounds = new PageBounds(pageable.getPageNumber(), pageable.getPageSize());
		}
		return bounds;
	}
	
	/**
	 * 询参数
	 * @param query 查询对象
	 * @return Map 查询参数
	 */
	protected Map<String, Object> getParams(T query) {
		Map<String, Object> params = BeanMapUtils.toMap(query);
		return params;
	}

	/**
	 * 获取分页查询参数
	 * @param query 查询对象
	 * @param pageable 分页对象
	 * @return Map 查询参数
	 */
	protected Map<String, Object> getParams(T query, Pageable pageable) {
		Map<String, Object> params = BeanMapUtils.toMap(query, getPageBounds(pageable));
		if (pageable != null && pageable.getSort() != null) {
			String sorting = pageable.getSort().toString();
			params.put("sorting", sorting.replace(":", ""));
		}
		return params;
	}

	/**
	 * 获取分页查询参数
	 * @param param 查询参数
	 * @param pageable 分页对象
	 * @return Map 查询参数
	 */
	protected Map<String, Object> getParams(Map param, Pageable pageable) {
		Map<String, Object> params = BeanMapUtils.toMap(param, getPageBounds(pageable));
		if (pageable != null && pageable.getSort() != null) {
			String sorting = pageable.getSort().toString();
			params.put("sorting", sorting.replace(":", ""));
		}
		return params;
	}


	@Override
	public <V extends T> List<V> selectList(T query, Pageable pageable) {
		try {
			return sqlSessionTemplate.selectList(getSqlName(SqlId.SQL_SELECT), getParams(query),getPageBounds(pageable));
		} catch (Exception e) {
			throw new DaoException(String.format("根据分页对象查询列表出错！语句:%s", getSqlName(SqlId.SQL_SELECT)), e);
		}
	}

	@Override
	public <V extends T> Page<V> selectPageList(T query, Pageable pageable) {
		try {
			List<V> contentList = sqlSessionTemplate.selectList(getSqlName(SqlId.SQL_SELECT),
					getParams(query, pageable));
			return new PageImpl<V>(contentList, pageable, this.selectCount(query));
		} catch (Exception e) {
			throw new DaoException(String.format("根据分页对象查询列表出错！语句:%s", getSqlName(SqlId.SQL_SELECT)), e);
		}
	}

	@Override
	public <K, V extends T> Map<K, V> selectMap(T query, String mapKey, Pageable pageable) {
		try {
			return sqlSessionTemplate.selectMap(getSqlName(SqlId.SQL_SELECT), getParams(query, pageable), mapKey);
		} catch (Exception e) {
			throw new DaoException(String.format("根据分页对象查询列表出错！语句:%s", getSqlName(SqlId.SQL_SELECT)), e);
		}
	}

	@Override
	public Long selectCount() {
		try {
			return sqlSessionTemplate.selectOne(getSqlName(SqlId.SQL_SELECT_COUNT));
		} catch (Exception e) {
			throw new DaoException(String.format("查询对象总数出错！语句：%s", getSqlName(SqlId.SQL_SELECT_COUNT)), e);
		}
	}

	@Override
	public Long selectCount(T query) {
		try {
			Map<String, Object> params = BeanMapUtils.toMap(query);
			return sqlSessionTemplate.selectOne(getSqlName(SqlId.SQL_SELECT_COUNT), params);
		} catch (Exception e) {
			throw new DaoException(String.format("查询对象总数出错！语句：%s", getSqlName(SqlId.SQL_SELECT_COUNT)), e);
		}
	}

	@Override
	public <T> int insert(T entity) {
		return insert(false,entity);
	}
	
	public <T> int insert(boolean batch,T entity) {
		Assert.notNull(entity);
		SqlSession  sqlSession=batch?sqlSessionTemplateBatch:sqlSessionTemplate;
		try {
			return sqlSession.insert(getSqlName(SqlId.SQL_INSERT), entity);
		} catch (Exception e) {
			throw new DaoException(String.format("添加对象出错！语句：%s", getSqlName(SqlId.SQL_INSERT)), e);
		}
	}

	@Override
	public int delete(T query) {
		Assert.notNull(query);
		try {
			Map<String, Object> params = BeanMapUtils.toMap(query);
			return sqlSessionTemplate.delete(getSqlName(SqlId.SQL_DELETE), params);
		} catch (Exception e) {
			throw new DaoException(String.format("删除对象出错！语句：%s", getSqlName(SqlId.SQL_DELETE)), e);
		}
	}

	@Override
	public int deleteById(int id) {
		 return deleteById(false,id);
	}
	
	public int deleteById(boolean batch,int id) {
		Assert.notNull(id);
		SqlSession  sqlSession=batch?sqlSessionTemplateBatch:sqlSessionTemplate;
		try {
			return sqlSession.delete(getSqlName(SqlId.SQL_DELETE_BY_ID), id);
		} catch (Exception e) {
			throw new DaoException(String.format("根据ID删除对象出错！语句：%s", getSqlName(SqlId.SQL_DELETE_BY_ID)), e);
		}
	}
	

	@Override
	public int deleteAll() {
		try {
			return sqlSessionTemplate.delete(getSqlName(SqlId.SQL_DELETE));
		} catch (Exception e) {
			throw new DaoException(String.format("删除所有对象出错！语句：%s", getSqlName(SqlId.SQL_DELETE)), e);
		}
	}

	@Override
	public int updateById(T entity) {
		Assert.notNull(entity);
		try {
			return sqlSessionTemplate.update(getSqlName(SqlId.SQL_UPDATE_BY_ID), entity);
		} catch (Exception e) {
			throw new DaoException(String.format("根据ID更新对象出错！语句：%s", getSqlName(SqlId.SQL_UPDATE_BY_ID)), e);
		}
	}

	@Override
	public int updateByIdSelective(T entity) {
		return updateByIdSelective(false,entity);
	}
	
	public int updateByIdSelective(boolean batch,T entity) {
		Assert.notNull(entity);
		SqlSession  sqlSession=batch?sqlSessionTemplateBatch:sqlSessionTemplate;
		try {
			return sqlSession.update(getSqlName(SqlId.SQL_UPDATE_BY_ID_SELECTIVE), entity);
		} catch (Exception e) {
			throw new DaoException(String.format("根据ID更新对象某些属性出错！语句：%s", getSqlName(SqlId.SQL_UPDATE_BY_ID_SELECTIVE)),
					e);
		}
	}

	@Override
	public void deleteByIdInBatch(List<Integer> idList) {
		if (idList == null || idList.isEmpty())
			return;
		for (Integer id : idList) {
			this.deleteById(true,id);
		}
	}

	@Override
	public void updateInBatch(List<T> entityList) {
		if (entityList == null || entityList.isEmpty())
			return;
		for (T entity : entityList) {
			//TODO:暂不使用batch
			this.updateByIdSelective(false,entity);
		}
	}

	@Override
	public int[] insertInBatch(List<T> entityList) {
		if (entityList == null || entityList.isEmpty())
			return null;
		
		int[] insertPKs = new int[entityList.size()];
		int i = 0;
		for (T entity : entityList) {
			//TODO:暂不使用batch
			insertPKs[i++] = this.insert(false,entity);
		}
		return insertPKs;
	}
	
	@Override
	public int[] insertBatch(List<T> entityList) {
		return insertInBatch(entityList);
	}

	@Override
	public <V extends T> List<V> selectList(Map<String, Object> query) {
		if(null != query && query.size() >0){ 
			return sqlSessionTemplate.selectList(getSqlName(SqlId.SQL_SELECT_MAP),query);
		}
		return null;
	}
 
	@Override
	public Long selectCount(Map<String, String> query) {
		
		return null;
	}

    public JdbcTemplate getJdbcTemplate(){
        return jdbcTemplate;
    }

    public SqlSession getSQLSessionTemplate(){
        return sqlSessionTemplate;
    }
    @PostConstruct
    public void init(){
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int[] executeJDBCBatch(String key,List<T> entityList){
        {
			if(null==entityList||entityList.size()==0){
				return new int[0];
			}

            final int batchSize = 1000;

            final Configuration configuration = getSQLSessionTemplate().getConfiguration();
            MappedStatement ms = configuration.getMappedStatement(getSqlName(key));
            final BoundSql boundSql=  ms.getBoundSql(entityList.get(0));
            String sql =boundSql.getSql();


            List<List<T>> batchLists = Lists.partition(entityList, batchSize);
            int[] ret=new int[entityList.size()];
            int destPos = 0;
            for(final List<T> batch : batchLists) {
                int res[]= getJdbcTemplate().batchUpdate(sql,new BatchPreparedStatementSetter(){
                    @Override
                    public int getBatchSize() {
                        return batch.size();
                    }
                    @Override
                    public void setValues(PreparedStatement ps, int index)
                            throws SQLException {
                        T parameterObject= batch.get(index);
                        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
                        if (parameterMappings != null) {
                            for (int i = 0; i < parameterMappings.size(); i++) {
                                ParameterMapping parameterMapping = parameterMappings.get(i);
                                if (parameterMapping.getMode() != ParameterMode.OUT) {
                                    Object value;
                                    String propertyName = parameterMapping.getProperty();
                                    if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                                        value = boundSql.getAdditionalParameter(propertyName);
                                    } else if (parameterObject == null) {
                                        value = null;
                                    } else {
                                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                                        value = metaObject.getValue(propertyName);
                                    }
                                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                                    JdbcType jdbcType = parameterMapping.getJdbcType();
                                    if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
                                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                                }
                            }
                        }
                    }

                });
                System.arraycopy(res, 0, ret, destPos, res.length);
                destPos+=res.length;
            }
            return ret;
        }
    }

}
